using System;
using System.Reflection;
using Microsoft.Practices.ObjectBuilder2;
using Unity.Extensions;
using Xunit;

namespace Unity.Extensions
{
    public class InterfaceInterceptionReflectorTest
    {
        [Fact]
        public void CanAddHandlersInInheritedClass()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(IOneMethod).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<IOneMethod, DerivedWithAddedIntercepts>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(DerivedWithAddedIntercepts)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(2, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][1]);
        }

        [Fact]
        public void InterceptionIsInherited()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(IOneMethod).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<IOneMethod, DerivedOneMethod>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(DerivedOneMethod)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void NonPublicInterfaceNotCompatible()
        {
            PolicyList policies = new PolicyList();

            Assert.Throws<InvalidOperationException>(
                () => InterceptionReflector.Reflect<INonPublicInterface,
                          NonPublicInterface>(policies, new StubObjectFactory()));
        }

        [Fact]
        public void OneInterceptedMethod()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(IOneMethod).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<IOneMethod, OneMethod>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(OneMethod)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void ReflectShouldHappenOnGenericBaseClass()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(IGeneric<>).GetMethod("DoSomething");

            InterceptionReflector.Reflect<IGeneric<int>, Generic<int>>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(Generic<>)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void RequestingToBuildInterface1WillNotInterceptedInterface2Methods()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(IInterface1).GetMethod("InterceptedMethod1");

            InterceptionReflector.Reflect<IInterface1, TwoInterfaceClass>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(TwoInterfaceClass)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(1, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
        }

        [Fact]
        public void RequestingToBuildNonInterfaceMeansMethodsNotIntercepted()
        {
            PolicyList policies = new PolicyList();

            InterceptionReflector.Reflect<OneMethod>(policies, new StubObjectFactory());

            Assert.Equal(0, policies.Count);
        }

        [Fact]
        public void TwoAttributesOnOneMethod()
        {
            PolicyList policies = new PolicyList();
            MethodBase method = typeof(IOneMethod).GetMethod("InterceptedMethod");

            InterceptionReflector.Reflect<IOneMethod, OneMethodTwoAttributes>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(OneMethodTwoAttributes)));

            Assert.Equal(1, policy.Count);
            Assert.Equal(2, policy[method].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][0]);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method][1]);
        }

        [Fact]
        public void TwoInterceptedMethods()
        {
            PolicyList policies = new PolicyList();
            MethodBase method1 = typeof(ITwoMethods).GetMethod("InterceptedMethod1");
            MethodBase method2 = typeof(ITwoMethods).GetMethod("InterceptedMethod2");

            InterceptionReflector.Reflect<ITwoMethods, TwoMethods>(policies, new StubObjectFactory());
            IInterfaceInterceptionPolicy policy = policies.Get<IInterfaceInterceptionPolicy>(new NamedTypeBuildKey(typeof(TwoMethods)));

            Assert.Equal(2, policy.Count);
            Assert.Equal(1, policy[method1].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method1][0]);
            Assert.Equal(1, policy[method2].Count);
            Assert.IsAssignableFrom(typeof(RecordingHandler), policy[method2][0]);
        }

        public interface IGeneric<T>
        {
            void DoSomething(T data);
        }

        public interface IInterface1
        {
            void InterceptedMethod1();
        }

        public interface IInterface2
        {
            void InterceptedMethod2();
        }

        public interface IOneMethod
        {
            void InterceptedMethod();
        }

        public interface ITwoMethods
        {
            void InterceptedMethod1();
            void InterceptedMethod2();
        }

        internal class DerivedOneMethod : OneMethod {}

        internal class DerivedWithAddedIntercepts : OneMethod
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            public override void InterceptedMethod() {}
        }

        internal class Generic<T> : IGeneric<T>
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            public void DoSomething(T data) {}
        }

        internal interface INonPublicInterface
        {
            void InterceptedMethod();
        }

        internal class NonPublicInterface : INonPublicInterface
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod() {}
        }

        internal class OneMethod : IOneMethod
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            public virtual void InterceptedMethod() {}

            public void NonInterceptedMethod() {}
        }

        internal class OneMethodTwoAttributes : IOneMethod
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            [InterfaceIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod() {}
        }

        internal class TwoInterfaceClass : IInterface1, IInterface2
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod1() {}

            [InterfaceIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod2() {}
        }

        internal class TwoMethods : ITwoMethods
        {
            [InterfaceIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod1() {}

            [InterfaceIntercept(typeof(RecordingHandler))]
            public void InterceptedMethod2() {}
        }
    }
}